﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Linq;
using System.Transactions;
using System.Xml.Linq;
using Xtensive.Collections;
using Xtensive.Core;
using Xtensive.IoC;
using Xtensive.Orm;
using Xtensive.Orm.Configuration;
using Xtensive.Orm.Operations;
using Transaction = Xtensive.Orm.Transaction;
using TransactionScope = Xtensive.Orm.TransactionScope;

namespace LINQPadDriverHelper
{
  // ReSharper disable once ClassNeverInstantiated.Global
  public class LINQPadSession : IVersionSetProvider, IContext<SessionScope>, IHasExtensions, ISession
  {
    private readonly Domain domain;
    private readonly Session session;
    private readonly TransactionScope tran;


    private Stopwatch commandTimer;
    private Stopwatch linqTimer;
    private List<Tuple<string, string>> warnings;


    internal static IDriver Driver { get; private set; }

    // ReSharper disable once MemberCanBePrivate.Global
    protected Session Session
    {
      get { return session; }
    }

    // ReSharper disable once UnusedMember.Global
    public SessionEventAccessor Events
    {
      get { return session.Events; }
    }

    // ReSharper disable once UnusedMember.Global
    public SessionEventAccessor SystemEvents
    {
      get { return session.SystemEvents; }
    }

    // ReSharper disable once UnusedMember.Global
    public QueryEndpoint Query
    {
      get { return session.Query; }
    }

    // ReSharper disable once UnusedMember.Global
    public Transaction Transaction
    {
      get { return session.Transaction; }
    }

    // ReSharper disable once UnusedMember.Global
    public SessionConfiguration Configuration
    {
      get { return session.Configuration; }
    }

    // ReSharper disable once UnusedMember.Global
    public string Name
    {
      get { return session.Name; }
    }

    // ReSharper disable once UnusedMember.Global
    public bool IsDisconnected
    {
      get { return session.IsDisconnected; }
    }

    // ReSharper disable once UnusedMember.Global
    public DisconnectedState DisconnectedState
    {
      get { return session.DisconnectedState; }
    }

    // ReSharper disable once UnusedMember.Global
    public OperationRegistry Operations
    {
      get { return session.Operations; }
    }

    // ReSharper disable once UnusedMember.Global
    public int? CommandTimeout
    {
      get { return session.CommandTimeout; }
      set { session.CommandTimeout = value; }
    }

    // ReSharper disable once UnusedMember.Global
    public IServiceContainer Services
    {
      get { return session.Services; }
    }

    // ReSharper disable once UnusedMember.Global
    public Domain Domain
    {
      get { return domain; }
    }

    public SessionScope Activate()
    {
      return session.Activate();
    }

    // ReSharper disable once UnusedMember.Global
    public SessionScope Activate(bool checkSwitching)
    {
      return session.Activate(checkSwitching);
    }

    // ReSharper disable once UnusedMember.Global
    public void CancelChanges()
    {
      session.CancelChanges();
    }

    public VersionSet CreateVersionSet(IEnumerable<Key> keys)
    {
      return ((IVersionSetProvider) session).CreateVersionSet(keys);
    }

    // ReSharper disable once UnusedMember.Global
    public IDisposable DisableSaveChanges(IEntity target)
    {
      return session.DisableSaveChanges(target);
    }

    // ReSharper disable once UnusedMember.Global
    public IDisposable DisableSaveChanges()
    {
      return session.DisableSaveChanges();
    }

    public void Dispose()
    {
      if (tran!=null)
        tran.Dispose();
      session.Dispose();
    }

    // ReSharper disable once UnusedMember.Global
    public void NotifyChanged()
    {
      session.NotifyChanged();
    }

    // ReSharper disable once UnusedMember.Global
    public void NotifyChanged(NotifyChangedOptions options)
    {
      session.NotifyChanged(options);
    }

    // ReSharper disable once UnusedMember.Global
    public TransactionScope OpenTransaction()
    {
      return session.OpenTransaction();
    }

    // ReSharper disable once UnusedMember.Global
    public TransactionScope OpenTransaction(IsolationLevel isolationLevel)
    {
      return session.OpenTransaction(isolationLevel);
    }

    // ReSharper disable once UnusedMember.Global
    public TransactionScope OpenTransaction(TransactionOpenMode mode)
    {
      return session.OpenTransaction(mode);
    }

    // ReSharper disable once UnusedMember.Global
    public TransactionScope OpenTransaction(TransactionOpenMode mode, IsolationLevel isolationLevel)
    {
      return session.OpenTransaction(mode, isolationLevel);
    }

    // ReSharper disable once UnusedMember.Global
    public IDisposable OverrideCommandTimeout(int? newTimeout)
    {
      return session.OverrideCommandTimeout(newTimeout);
    }

    [Obsolete]
    // ReSharper disable once UnusedMember.Global
    public void Persist()
    {
      session.Persist();
    }

    // ReSharper disable once UnusedMember.Global
    public void Remove<T>(IEnumerable<T> entities) where T : IEntity
    {
      session.Remove(entities);
    }

    public void SaveChanges()
    {
      session.SaveChanges();
    }

    // ReSharper disable once UnusedMember.Global
    public void Validate()
    {
      session.Validate();
    }

    #region Non-public methods

    public void Commit()
    {
      tran.Complete();
      tran.Dispose();
    }

    IDisposable IContext.Activate()
    {
      return ((IContext) session).Activate();
    }

    private void DomainOnSessionOpen(object sender, SessionEventArgs sessionEventArgs)
    {
    }

    private void EventsOnDbCommandExecuted(object sender, DbCommandEventArgs e)
    {
      TimeSpan elapsed = commandTimer.Elapsed;
      string s = CommandFormatter1.Format(e.Command);
      Driver.LogSql(s, elapsed, warnings);
      commandTimer = null;
      warnings = null;
      if (linqTimer!=null)
        linqTimer.Start();
    }

    private void EventsOnDbCommandExecuting(object sender, DbCommandEventArgs e)
    {
      if (linqTimer!=null)
        linqTimer.Stop();
      warnings = GetWarnings(e.Command);
      commandTimer = Stopwatch.StartNew();
    }

    private void EventsOnLinqExecuted(object sender, QueryEventArgs e)
    {
      TimeSpan elapsed = linqTimer.Elapsed;
      Driver.LogExpression(e.Expression.ToString(true), elapsed);
      linqTimer = null;
    }

    private void EventsOnLinqExecuting(object sender, QueryEventArgs e)
    {
      linqTimer = Stopwatch.StartNew();
    }

    private static List<Tuple<string, string>> GetWarnings(DbCommand command)
    {
      if (!(command is SqlCommand))
        return null;
      DbCommand cmd = command.Connection.CreateCommand();
      cmd.Transaction = command.Transaction;
      cmd.CommandText = "set showplan_xml on;";
      cmd.ExecuteNonQuery();
      var s = (string) command.ExecuteScalar();
      cmd.CommandText = "set showplan_xml off;";
      cmd.ExecuteNonQuery();
      if (s!=null) {
        XElement xml = XElement.Parse(s);
        XNamespace ns = "http://schemas.microsoft.com/sqlserver/2004/07/showplan";
        XElement e = xml.Descendants(ns + "MissingIndexes").FirstOrDefault();
        if (e!=null)
          return new List<Tuple<string, string>> {Tuple.Create("Missing indexes", e.ToString())};
      }
      return null;
    }

    #endregion

    #region Operators

    public static implicit operator Session(LINQPadSession session)
    {
      return session.Session;
    }

    #endregion

    #region IContext<SessionScope> Members

    public bool IsActive
    {
      get { return session.IsActive; }
    }

    #endregion

    #region IHasExtensions Members

    public IExtensionCollection Extensions
    {
      get { return session.Extensions; }
    }

    #endregion

    public LINQPadSession(IDriver driver)
    {
      Driver = driver;
      domain = Helper.GetDomain();
      domain.SessionOpen += DomainOnSessionOpen;
      session = domain.OpenSession();
      session.Events.DbCommandExecuting += EventsOnDbCommandExecuting;
      session.Events.DbCommandExecuted += EventsOnDbCommandExecuted;
      session.Events.QueryExecuting += EventsOnLinqExecuting;
      session.Events.QueryExecuted += EventsOnLinqExecuted;
      session.Activate();
      tran = session.OpenTransaction(IsolationLevel.Serializable);
    }
  }
}

// ReSharper restore MemberCanBePrivate.Global
// ReSharper restore UnusedMember.Global